﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Input;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using FarseerPhysics.Common;
using Animation;

namespace MapLibrary
{
    class LevelEditor : KeyboardInputListener, MouseInputListener
    {

        /// <summary>
        /// Represents the different editing modes of the editor
        /// </summary>
        private enum EditingMode { MapEdit, CollisionEdit, SpriteEdit };

        /// <summary>
        /// The current mode of the editor
        /// </summary>
        private EditingMode Mode;

        /**
         * Represents the level the editor is currently working with.
         */
        private Level mLevel;

        private List<EditorLayer> mEditorLayers;

        /**
         * Current layer that the editor is modifying.
         */
        private int mCurrentLayer;

        private SpriteManager mSpriteManager;

        /**
         * The map, taken from the level upon editing.
         */
        private Map mMap;

        /**
         * Manages the saving system for the LevelEditor
         */
        private SaveManager mSaveManager;

        /// <summary>
        /// Handles adding and viewing the collision objects of the
        /// Level
        /// </summary>
        private CollisionObjectManager collisionManager;

        private PaletteEditorPanel mPalettePanel;

        private SpriteTypePanel mSpritePanel;

        /**
         * Reference to the World's Viewport.
         */
        private WorldViewport mViewport;

        /**
         * Base textures and font necessary for the operation of the level
         * editor.  The RectangleBase is the base sprite for the rectangles
         * drawn around the nodes.
         */
        private SpriteFont mFont;

        /**
         * Graphics device where all level editor objects are drawn.
         */
        private GraphicsDevice mGraphicsDevice;

        /**
         * Object that handles all transformations that the user applies
         * on the editor nodes.
         */
        private TransformationManager mTransformationManager;

        /**
         * Stores if the editor is enabled, necessary information for the
         * game world.  An editor gets enabled when Edit(Level) is called,
         * and disabled when Disable() is called.
         */
        private bool isEnabled;

        private Vector2 mMousePosition;

        private ContentManager mContentManager;

        public LevelEditor(GraphicsDevice Device, WorldViewport pViewport, ContentManager pContentManager)
        {
            mCurrentLayer = 0;
            mGraphicsDevice = Device;
            isEnabled = false;
            mViewport = pViewport;
            mEditorLayers = new List<EditorLayer>();
            mContentManager = pContentManager;

            Mode = EditingMode.MapEdit;

            //initialize editors
            mTransformationManager = new TransformationManager(mViewport);
            mSaveManager = new SaveManager();
            mPalettePanel = new PaletteEditorPanel();
            collisionManager = new CollisionObjectManager();
            mSpritePanel = new SpriteTypePanel();
            mSpriteManager = new SpriteManager();
        }

        public Level Level
        {
            set { mLevel = value; }
            get { return mLevel; }
        }

        public bool Enabled
        {
            get { return isEnabled; }
            set { isEnabled = value; }
        }

        public int CurrentLayer
        {
            set { mCurrentLayer = value; }
            get { return mCurrentLayer; }
        }

        /**
         * Load all editor content.  Notice that this differs from LoadEditing.
         * 
         * LoadEditor is used for loading the editor in general.  It must be called after the
         * editor is constructed.  LoadEditing on the other hand gets called whenever you want
         * to enable the editor on the level.
         */
        public void LoadEditor(ContentManager Content)
        {
            mFont = Content.Load<SpriteFont>(@"Arial");

            mPalettePanel.Load(Content);
            mSpritePanel.Load(Content);
            collisionManager.LoadContent(Content);
        }

        /**
         * Loads the editor on the specified level.  Notice that this differs from LoadEditor.
         * 
         * LoadEditor is used for loading the editor in general.  It must be called after the
         * editor is constructed.  LoadEditing on the other hand gets called whenever you want
         * to enable the editor on the level.
         */
        public void LoadEditing(Level NewLevel, ContentManager Manager)
        {
            this.isEnabled = true;
            this.mLevel = NewLevel;
            this.mMap = mLevel.Map;
            this.mPalettePanel.LoadPalette(mMap.Palette);
            this.mSpritePanel.LoadSpritePalette(NewLevel.SpriteTypes);
            this.collisionManager.EditLevel(NewLevel);
            this.mSpriteManager.EditLevel(NewLevel);

            LoadEditorLayers(Manager);
        }

        public void LoadEditorLayers(ContentManager pContent)
        {
            int i = 0;
            foreach (Layer tLayer in mMap.Layers)
            {
                EditorLayer tEditorLayer;
                if (i == 0)
                    tEditorLayer = new TiledEditorLayer(tLayer);
                else
                    tEditorLayer = new EditorLayer(tLayer);

                tEditorLayer.Load(pContent, mGraphicsDevice);
                mEditorLayers.Add(tEditorLayer);
                i++;
            }

            UpdateTransparency();
        }

        public void SwitchEditingLayer(int newLayer)
        {
            if (newLayer != mCurrentLayer)
            {
                int tOldLayer = mCurrentLayer;
                mCurrentLayer = newLayer;

                mEditorLayers[mCurrentLayer].ActivateLayer();
                mEditorLayers[tOldLayer].DeactivateLayer();
            }
        }

        private void DeactivateBoundingBoxes()
        {
            mEditorLayers[mCurrentLayer].DeactivateLayer();
        }

        private void ReactivateBoundingBoxes()
        {
            mEditorLayers[mCurrentLayer].ActivateLayer();
        }

        private void SwitchMode(EditingMode newMode)
        {
            if (newMode == EditingMode.CollisionEdit)
            {
                DeactivateBoundingBoxes();
                mPalettePanel.Enabled = false;
            }
            else if (newMode == EditingMode.CollisionEdit)
            {
                ReactivateBoundingBoxes();
                mPalettePanel.Enabled = true;
            }
            else
            {
                mPalettePanel.Enabled = false;
            }
            Mode = newMode;
        }

        /// <summary>
        /// This updates the transparency of the whole editor.  This should be
        /// rarely called, as it iterates over every single editor node.
        /// </summary>
        public void UpdateTransparency()
        {
            for (int i = 0; i < mEditorLayers.Count; i++)
            {
                if (i == mCurrentLayer)
                    mEditorLayers[i].ActivateLayer();
                else
                    mEditorLayers[i].DeactivateLayer();
            }
        }

        public void Save()
        {
            mSaveManager.Save(mLevel, mMap);
        }

        /**
         * Disable the editor.
         */
        public void Disable()
        {
            this.isEnabled = false;

            foreach (EditorLayer tLayer in mEditorLayers)
            {
                tLayer.ActivateLayer();
            }

            this.mEditorLayers.Clear();

            this.mPalettePanel.Disable();
            this.mSpritePanel.Disable();
        }


        /**
         * Draw the level editor on the sprite batch within the specified viewport.
         */
        public void Draw(SpriteBatch Batch)
        {

            DrawLayers(Batch);

            if (Mode == EditingMode.CollisionEdit)
            {
                collisionManager.Draw(Batch, mViewport);
                mSpriteManager.Draw(Batch, mViewport);
            }
            else if (Mode == EditingMode.SpriteEdit)
            {
                mSpritePanel.Draw(Batch);
                mSpriteManager.Draw(Batch, mViewport);
            }

            //draw panel
            if (mPalettePanel.Enabled)
                mPalettePanel.Draw(Batch);

            //editing indication on the screen.
            if (Mode == EditingMode.MapEdit)
                Batch.DrawString(mFont, "Editing (Map)", new Vector2(25, 450), Color.Wheat);
            else if (Mode == EditingMode.CollisionEdit)
                Batch.DrawString(mFont, "Editing (Collision)", new Vector2(25, 450), Color.Wheat);
            else
                Batch.DrawString(mFont, "Editing (Sprites)", new Vector2(25, 450), Color.Wheat);
        }

        private void DrawLayers(SpriteBatch Batch)
        {
            int layerCount = 0;
            foreach (EditorLayer tLayer in mEditorLayers)
            {
                tLayer.Draw(Batch, mViewport);
                if ((layerCount == mCurrentLayer) && (Mode == EditingMode.MapEdit))
                {
                    //draw selected texture
                    Texture2D tSelectedTexture = mPalettePanel.GetSelectedTexture();
                    if (tSelectedTexture != null)
                    {
                        Rectangle rect = tSelectedTexture.Bounds;
                        Point temp = rect.Center;
                        rect.X = (int)mMousePosition.X - temp.X;
                        rect.Y = (int)mMousePosition.Y - temp.Y;
                        Batch.Draw(
                            tSelectedTexture,
                            rect,
                            Color.White);
                    }
                }

                layerCount++;
            }
        }


        public void UpdateKeyboardInput(GameInput Input)
        {
            if (isEnabled)
            {
                if (Input.IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl)
                    && Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.P))
                    Save();

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad1))
                    this.SwitchEditingLayer(1);
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad2))
                    this.SwitchEditingLayer(2);
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad3))
                    this.SwitchEditingLayer(3);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad4))
                //    this.SwitchEditingLayer(4);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad5))
                //    this.SwitchEditingLayer(5);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad6))
                //    this.SwitchEditingLayer(6);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad7))
                //    this.SwitchEditingLayer(7);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad8))
                //    this.SwitchEditingLayer(8);
                //else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad9))
                //    this.SwitchEditingLayer(9);
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.NumPad0))
                    this.SwitchEditingLayer(0);

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Left))
                {
                    if (Mode == EditingMode.MapEdit)
                        mPalettePanel.MoveSelectionLeft();
                    else if (Mode == EditingMode.SpriteEdit)
                        mSpritePanel.MoveSelectionLeft();
                }
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Right))
                {
                    if (Mode == EditingMode.MapEdit)
                        mPalettePanel.MoveSelectionRight();
                    else if (Mode == EditingMode.SpriteEdit)
                        mSpritePanel.MoveSelectionRight();
                }

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.D1))
                {
                    collisionManager.SwitchMode(0);
                }
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.D2))
                {
                    collisionManager.SwitchMode(1);
                }
                else if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.D3))
                {
                    collisionManager.SwitchMode(2);
                }

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Tab))
                {
                    if (Mode == EditingMode.MapEdit)
                        mPalettePanel.Enabled = !mPalettePanel.Enabled;
                    else
                        collisionManager.SwitchEditingMode();
                }


                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.C))
                {
                    SwitchMode(EditingMode.CollisionEdit);
                }

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.M))
                {
                    SwitchMode(EditingMode.MapEdit);
                }

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.V))
                {
                    SwitchMode(EditingMode.SpriteEdit);
                }

                if (Input.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Enter))
                {
                    if (Mode == EditingMode.CollisionEdit)
                    {
                        //if (mSpriteManager.CurrentSprite != null)
                        //{
                        //    collisionManager.FinalizeCollisionObject(
                        //        this.mViewport,
                        //        mSpriteManager.CurrentSprite);
                        //}
                        //else
                        collisionManager.FinalizeCollisionObject(this.mViewport);
                    }

                }
            }
        }

        public void OnKeyboardRegister(GameInput Input)
        {
            return;
        }

        /**
         * Handles manipulation of editor nodes.
         */
        public void UpdateMouseInput(GameInput Input)
        {
            //find node, if any, that is hovered over.
            if (isEnabled)
            {
                mMousePosition = Input.GetMousePosition();

                if (Mode == EditingMode.SpriteEdit && Input.IsLeftMouseButtonPressed())
                {
                    if (!mSpriteManager.HasHover(mViewport.ViewportCoordsToWorldCoords(mMousePosition)))
                    {
                        SpriteType type = mSpritePanel.GetSelectedSpriteType();
                        mSpriteManager.PlaceSprite(
                            type,
                            mViewport.ViewportCoordsToWorldCoords(Input.GetMousePosition()));
                    }
                }

                if (!mPalettePanel.Enabled)
                {
                    ITransformable toTransform = GetHoverTransformable(Input);

                    if (Mode == EditingMode.CollisionEdit)
                    {
                        if ((toTransform == null) && (Input.IsLeftMouseButtonPressed()))
                        {
                            if (collisionManager.EditMode == CollisionObjectManager.EditingMode.Hull)
                                collisionManager.PlaceObject(mViewport.ViewportCoordsToWorldCoords(Input.GetMousePosition()));
                            else
                                collisionManager.SetSelection(Input, mViewport);

                        }
                        else
                            mTransformationManager.Update(Input, toTransform);
                    }
                    else if (Mode == EditingMode.MapEdit)
                        mTransformationManager.Update(Input, toTransform);
                    else
                    {
                        mTransformationManager.Update(Input, toTransform);
                    }
                }
                else
                {
                    if (Input.IsLeftMouseButtonPressed())
                    {
                        if (Mode == EditingMode.MapEdit)
                        {
                            Texture2D tTextureToAdd = mPalettePanel.GetSelectedTexture();
                            MapNode tNode = MapNode.ConstructDefaultMapNode(mPalettePanel.GetAssetNameForTexture(tTextureToAdd));
                            tNode.Load(mContentManager);
                            EditorNode tEditorNode = new EditorNode(tNode);
                            tEditorNode.Load(mGraphicsDevice, mContentManager);

                            tEditorNode.Position = mViewport.ViewportCoordsToWorldCoords(mMousePosition);

                            mEditorLayers[mCurrentLayer].AddNode(tEditorNode);
                            mMap.Layers[mCurrentLayer].AddNode(tNode);
                        }
                    }
                }
            }
        }

        private void AddSprite()
        {

        }

        private ITransformable GetHoverTransformable(GameInput input)
        {
            if (Mode == EditingMode.CollisionEdit)
            {
                return collisionManager.GetHover(input, mViewport);
            }
            else if (Mode == EditingMode.MapEdit)
            {
                mEditorLayers[mCurrentLayer].UpdateNodeHover(input, mViewport);
                EditorNode tHoverNode = mEditorLayers[mCurrentLayer].GetHoveredNode(input, mViewport);
                return tHoverNode;
            }
            else
            {
                return mSpriteManager.GetHovered(mViewport.ViewportCoordsToWorldCoords(input.GetMousePosition()));
            }
        }

        public void OnMouseRegister(GameInput Input)
        {
            return;
        }
    }
}
